#include <stdio.h>
#include <stdint.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

#include <ZLog.h>
#include <PcapParser.h>

#define PCAP_FILE_HDR_SIZE 24
#define PCAP_PKT_HDR_SIZE 16

typedef struct PcapHeaderS PcapHeaderT;

/*
*    +------------------------------+
*    |        Magic number          |
*    +--------------+---------------+
*    |Major version | Minor version |
*    +--------------+---------------+
*    |      Time zone offset        |
*    +------------------------------+
*    |     Time stamp accuracy      |
*    +------------------------------+
*    |       Snapshot length        |
*    +------------------------------+
*    |   Link-layer header type     |
*    +------------------------------
*
*    The magic number,
*       when read by a host with the same byte order as the host that wrote the
*       file, will have the value 0xa1b2c3d4, and, when read by a host with the
*       opposite byte order as the host that wrote  the  file,  will  have  the
*       value  0xd4c3b2a1. 
*/

struct PcapHeaderS
{
    uint32_t magic;
    uint16_t versionMajor;
    uint16_t versionMinor;
    uint32_t timezoneOffset;
    uint32_t timestampAccu;
    uint32_t snapLen;
    uint32_t linkType;
};

struct PcapS
{
    char *path;
    FILE *fd;
    int64_t size;
    PcapHeaderT hdr;
    int byteOrder; /* 0: the same byte order; 1: opposite byte order  */
//    int32_t payloadOffset;
};

#define ByteOrderTransU16(out, in)                                  \
    do                                                              \
    {                                                               \
        *(uint8_t *)(out) = *(((uint8_t *)(in)) + 1);              \
        *(((uint8_t *)(out)) + 1) = *(uint8_t *)(in);              \
    }                                                               \
    while (0)

#define ByteOrderTransU32(out, in)                                  \
    do                                                              \
    {                                                               \
        *(uint8_t *)(out) = *(((uint8_t *)(in)) + 3);              \
        *(((uint8_t *)(out)) + 1) = *(((uint8_t *)(in)) + 2);     \
        *(((uint8_t *)(out)) + 2) = *(((uint8_t *)(in)) + 1);     \
        *(((uint8_t *)(out)) + 3) = *(uint8_t *)(in);              \
    }                                                               \
    while (0)

static int readU32(FILE *fd, int byteOrder, uint32_t *pVal)
{
    uint32_t value = 0;
    int ret = 0;
    
    ret = fread(&value, 4, 1, fd);
    if (ret != 1)
    {
        LogE("fread failure, errno(%d)", errno);
        return -1;
    }

    if (byteOrder == 0) /* the same byte order; */
    {
        *pVal = value;
    }
    else /*  */
    {
        ByteOrderTransU32(pVal, &value);
    }
    return 0;
}

static int readU16(FILE *fd, int byteOrder, uint16_t *pVal)
{
    uint16_t value = 0;
    int ret = 0;
    
    ret = fread(&value, 2, 1, fd);
    if (ret != 1)
    {
        LogE("fread failure, errno(%d)", errno);
        return -1;
    }

    if (byteOrder == 0) /* the same byte order; */
    {
        *pVal = value;
    }
    else
    {
        ByteOrderTransU16(pVal, &value);
    }
    
    return 0;
}

static int parseHeader(PcapT *pcap)
{
    int ret;
    ret = fseek(pcap->fd, 0, SEEK_SET);
    Z_ASSERT(ret == 0);

    ret = fread(&pcap->hdr.magic, 4, 1, pcap->fd);
    Z_ASSERT(ret == 1);

    if (pcap->hdr.magic == 0xa1b2c3d4)
    {
        pcap->byteOrder = 0;
        LogI("The same byte order as host");
    }
    else if (pcap->hdr.magic == 0xa1b2c3d4)
    {
        pcap->byteOrder = 1;
        LogI("The opposite byte order as host");
    }
    else
    {
        LogE("Not supoort format...");
        return -1;
    }

    ret = readU16(pcap->fd, pcap->byteOrder, &pcap->hdr.versionMajor);
    Z_ASSERT(ret == 0);
    
    ret = readU16(pcap->fd, pcap->byteOrder, &pcap->hdr.versionMinor);
    Z_ASSERT(ret == 0);

    ret = readU32(pcap->fd, pcap->byteOrder, &pcap->hdr.timezoneOffset);
    Z_ASSERT(ret == 0);
    
    ret = readU32(pcap->fd, pcap->byteOrder, &pcap->hdr.timestampAccu);
    Z_ASSERT(ret == 0);

    ret = readU32(pcap->fd, pcap->byteOrder, &pcap->hdr.snapLen);
    Z_ASSERT(ret == 0);

    ret = readU32(pcap->fd, pcap->byteOrder, &pcap->hdr.linkType);
    Z_ASSERT(ret == 0);
    
    return 0;
}

PcapT *PcapCreate(const char *path)
{
    PcapT *pcap = NULL;
    int ret;
    
    Z_ASSERT(path);
    
    pcap = malloc(sizeof(*pcap));
    memset(pcap, 0x00, sizeof(*pcap));

    pcap->path = strdup(path);
    pcap->fd = fopen(pcap->path, "r");
    Z_ASSERT(pcap->fd != NULL);
    
    ret = fseek(pcap->fd, 0, SEEK_END);
    Z_ASSERT(ret == 0);

    pcap->size = ftell(pcap->fd);
    Z_ASSERT(pcap->size >= PCAP_FILE_HDR_SIZE);

    ret = parseHeader(pcap);
    Z_ASSERT(ret == 0);

    LogD("pcap filesize: '%lld'", pcap->size);
    return pcap;
}

int PcapFetchPacket(PcapT *pcap, PcapPacketT **pPacket)
{
    PcapPacketT *packet = NULL;
    int ret;
    int64_t offset;

    *pPacket = NULL;
    offset = ftell(pcap->fd);
    if ((pcap->size - offset) <= PCAP_PKT_HDR_SIZE)
    {
        LogW("EOS...");
        goto failure;
    }

//    LogD("pos: '%lld'", offset);
    
    packet = malloc(sizeof(*packet));
    memset(packet, 0x00, sizeof(*packet));
    
    ret = readU32(pcap->fd, pcap->byteOrder, &packet->timeSec);
    Z_ASSERT(ret == 0);

    ret = readU32(pcap->fd, pcap->byteOrder, &packet->timeMs);
    Z_ASSERT(ret == 0);

    ret = readU32(pcap->fd, pcap->byteOrder, &packet->len);
    Z_ASSERT(ret == 0);

    ret = readU32(pcap->fd, pcap->byteOrder, &packet->unTruncatedLen);
    Z_ASSERT(ret == 0);

    packet->data = malloc(packet->len);

    if ((pcap->size - offset - PCAP_PKT_HDR_SIZE) < packet->len)
    {
        LogW("Data Not Enought, maybe EOS...");
        goto failure;
    }
    
    ret = fread(packet->data, 1, packet->len, pcap->fd);
    Z_ASSERT(ret == packet->len);

    *pPacket = packet;
    return 0;

failure:
    if (packet)
    {
        if (packet->data)
        {
            free(packet->data);
        }
        free(packet->data);
    }
    
    return -1;
}

void PcapDestroy(PcapT *pcap)
{
    Z_ASSERT(pcap);

    if (pcap->fd)
    {
        fclose(pcap->fd);
        pcap->fd = NULL;
    }

    if (pcap->path)
    {
        free(pcap->path);
        pcap->path = NULL;
    }

    free(pcap);
    
    return ;
}

